﻿using Dyao.Book.Application.Abstractions;
using Dyao.Book.Contract;
using Dyao.Book.Repository.Abstractions;
using Foundation.Core;
using Foundation.DistributeLock;
using Foundation.Uow;
using Microsoft.Extensions.DependencyInjection;

namespace Dyao.Book.Application
{
    public class BookContentApplication : BaseApplication, IBookContentApplication
    {
        private readonly IBookCatalogueContentRepository _bookCatalogueContentRepository;

        private readonly IBookCatalogueRepository _catalogueRepository;

        private readonly IDistributeLocker _distributeLocker;

        public BookContentApplication(IBookCatalogueContentRepository bookCatalogueContentRepository,
            IBookCatalogueRepository catalogueRepository,
            IDistributeLocker distributeLocker)
        {
            _bookCatalogueContentRepository = bookCatalogueContentRepository;
            _catalogueRepository = catalogueRepository;
            _distributeLocker = distributeLocker;
        }

        /// <inheritdoc cref="IBookContentApplication.GetListByChapterIdAsync"/>
        public async Task<List<BookContentDto>> GetListByChapterIdAsync(string chapterId, CancellationToken cancellationToken = default)
        {
            using (var uow = UnitOfWorkManager.Begin(isReadDb: true))
            {
                var result = await _bookCatalogueContentRepository.GetListByChapterIdAsync(chapterId, bc => new BookContentDto()
                {
                    Id = bc.ID,
                    Content = bc.Content,
                    WordsNumber = bc.WordsNumber,
                    Seq = bc.Seq,
                }, cancellationToken);

                return result;
            }
        }

        /// <inheritdoc cref="IBookContentApplication.CrawlContentAsync"/>
        public async Task<bool> CrawlContentAsync(string bookId, CancellationToken cancellationToken)
        {
            var bookSourceRepo = GetService<IBookMatchSourceRepository>();

            using (var uow = UnitOfWorkManager.Begin())
            {
                var sources = await bookSourceRepo.GetListByBookIdAsync(bookId, cancellationToken);
                if (sources.IsNullOrEmpty())
                    throw new BusinessException("该书籍未匹配书源，请匹配后再试.");

                var chapters = await _catalogueRepository.GetNotCrawlCompalteByBookIdAsync(bookId,
                bc => new CrawCatalogueContentDto()
                {
                    BookCatalogueId = bc.ID,
                    ContentLink = bc.ContentLink,
                    SourceKey = bc.SourceKey,
                }, cancellationToken);

                if (!chapters.Any())
                    throw new BusinessException($"未匹配章节源,请匹配后再试.");

                await CrawlContentAsync(bookId, chapters, cancellationToken);
            }

            return true;
        }

        public async Task CrawlContentAsync(TaskCenterDto taskCenter, CancellationToken cancellationToken)
        {
            await ServiceProvider.BeginSingleTaskAsync(async (context) =>
            {
                var craw = context.GetQueryFilterModel<CrawChapterDto>();

                string resource = $"CrawlContentAsync_{craw.BookId}";
                var redLock = await _distributeLocker.CreateBlockingLockAsync(resource, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10), TimeSpan.FromSeconds(5), cancellationToken);
                await redLock.UsingNonBlockingLockAsync(async () =>
                {
                    await CrawlContentAsync(craw.BookId, cancellationToken);
                    await AddBookCrawlContentLogAsync(craw.BookId, cancellationToken);
                }, () =>
                {
                    throw new NotImplementedException($"{craw.BookId}-正在爬取章节内容,请稍后再试");
                });

            }, taskCenter, cancellationToken);
        }

        /// <summary>
        /// 手动爬取
        /// </summary>
        /// <param name="chapterId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task ManualCrawlContentAsync(TaskCenterDto taskCenter, CancellationToken cancellationToken)
        {
            await ServiceProvider.BeginSingleTaskAsync(async (context) =>
            {
                var craw = context.GetQueryFilterModel<ManualCrawChapterDto>();
                var bookSourceRepo = GetService<IBookMatchSourceRepository>();

                string resource = $"CrawlContentAsync_{craw.BookId}";
                var redLock = await _distributeLocker.CreateBlockingLockAsync(resource, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10), TimeSpan.FromSeconds(5), cancellationToken);
                await redLock.UsingNonBlockingLockAsync(async () =>
                {
                    await context.AddProgressAsync(10); //更新进度
                    var chapterIds = craw.ChapterId.SplitDefaultToList();
                    using (var uow = UnitOfWorkManager.Begin())
                    {
                        var sources = await bookSourceRepo.GetListByBookIdAsync(craw.BookId, cancellationToken);
                        if (sources.IsNullOrEmpty())
                            throw new BusinessException("该书籍未匹配书源，请匹配后再试.");

                        var chapterEntities = await _catalogueRepository.GetListByIdsAsync(chapterIds, c => new { c.ID, c.CatalogueName, c.ContentLink, c.SourceKey, c.IsMatchSource, c.CrawlComplete }, cancellationToken);
                        var filterChapters = chapterEntities.Where(c => c.IsMatchSource);
                        if (!filterChapters.Any())
                            throw new BusinessException($"{chapterEntities.Select(c => c.CatalogueName).JoinAsString(",")},未匹配章节源,请匹配后再试.");

                        var crawlChapter = filterChapters.Select(f => new CrawCatalogueContentDto()
                        {
                            BookCatalogueId = f.ID,
                            ContentLink = f.ContentLink,
                            SourceKey = f.SourceKey,
                        });

                        await CrawlContentAsync(craw.BookId, crawlChapter.ToList(), cancellationToken, true);
                    }

                    await AddBookCrawlContentLogAsync(craw.BookId, cancellationToken);
                }, () =>
                {
                    throw new NotImplementedException($"{craw.BookId}-正在爬取章节内容,请稍后再试");
                });

            }, taskCenter, cancellationToken);
        }

        /// <summary>
        /// 爬取内容
        /// </summary>
        /// <param name="bookId">书籍编号</param>
        /// <param name="chapters">章节</param>
        /// <param name="cancellationToken">取消令牌</param>
        /// <returns></returns>
        private async Task CrawlContentAsync(string bookId, List<CrawCatalogueContentDto> chapters, CancellationToken cancellationToken, bool isManual = false)
        {
            if (chapters.IsNullOrEmpty())
                return;

            var bookSourceMgr = GetService<IBookSourceWarehouseManager>();

            using (var scope = ServiceProvider.CreateScope())
            {
                var serviceProvider = scope.ServiceProvider;

                using (var unitOfWork = UnitOfWorkManager.Begin(requiresNew: true))
                {
                    var builder = new BusinessChainBuilder<BookContentPluginsChain, BookContentPluginsContext>(serviceProvider);
                    var sourceSettings = await bookSourceMgr.GetSourceWarehousesForCacheAsync(cancellationToken);
                    var args = new BookContentPluginsArgs()
                    {
                        BookId = bookId,
                        SourceSettings = sourceSettings,
                        IsManual = isManual
                    };

                    builder.UsePlugin(typeof(QiDianBookContentPlugins));
                    builder.UsePlugin(typeof(WanZhengShuBookContentPlugins));

                    var chain = builder.Build();
                    var context = new BookContentPluginsContext(args);
                    context.Collection = chapters;
                    await chain.RunAsync(context, cancellationToken);
                }
            }
        }

        /// <summary>
        /// 添加书籍爬取内容日志
        /// </summary>
        /// <param name="bookId">书籍编号</param>
        /// <param name="cancellationToken">取消令牌</param>
        /// <returns></returns>
        private async Task AddBookCrawlContentLogAsync(string bookId, CancellationToken cancellationToken)
        {
            var commonMgr = GetService<ICommonManager>();
            using (var uow = UnitOfWorkManager.Begin(isTransactional: true))
            {
                try
                {
                    var log = new OperationLogDto()
                    {
                        OperationLog = "爬取章节内容完成",
                        SourceId = bookId,
                        OperationType = OperationTypeEnum.Book
                    };
                    await commonMgr.AddOperationLogsAsync(log);
                    await uow.SaveChangesAsync(cancellationToken);
                    await uow.CompleteAsync(cancellationToken);
                }
                catch (Exception)
                {
                    await uow.RollbackAsync(cancellationToken);
                    throw;
                }
            }
        }
    }
}